#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/platform_device.h>
#include <linux/device.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/kdev_t.h>
#include <linux/uaccess.h>
#include <linux/wait.h>
#include <linux/sched.h>
#include <linux/poll.h>

#include <asm/io.h>
#include <asm/uaccess.h>

#define API_ENTER() printk("func: %s at line: %d is called\r\n", __FUNCTION__, __LINE__);
#define API_EXIT()  printk("func: %s exit\r\n", __FUNCTION__);

#define LED2_MAGIC 'L'
#define LED3_MAGIC 'M'
#define LED4_MAGIC 'N'
#define LED5_MAGIC 'O'
/*
 * need arg = 1/2 
 */

#define LED2_ON	_IOW(LED2_MAGIC, 0, int)
#define LED2_OFF	_IOW(LED2_MAGIC, 1, int)
#define LED3_ON	_IOW(LED3_MAGIC, 0, int)
#define LED3_OFF	_IOW(LED3_MAGIC, 1, int)
#define LED4_ON	_IOW(LED4_MAGIC, 0, int)
#define LED4_OFF	_IOW(LED4_MAGIC, 1, int)
#define LED5_ON	_IOW(LED5_MAGIC, 0, int)
#define LED5_OFF	_IOW(LED5_MAGIC, 1, int)

#define SIZE  100
#define DEVICE_COUNT 4


static int major = 0;
static int minor = 0;
static struct cdev cdev;
static struct class * pClass = NULL;
char * ledminor = NULL;



static volatile unsigned int *gpx2con;
static volatile unsigned int *gpx2dat;

static volatile unsigned int *gpx1con;
static volatile unsigned int *gpx1dat;

static volatile unsigned int * gpf3con;
static volatile unsigned int * gpf3dat;
//led的初始化：ioremap, 初始值
int led_map_init(struct platform_device *pdev)
{
	int ret = 0;
	API_ENTER();
	gpx2con = ioremap(pdev->resource[0].start,pdev->resource[0].end - pdev->resource[0].start);
	gpx1con = ioremap(pdev->resource[1].start,pdev->resource[1].end - pdev->resource[1].start);
	gpf3con = ioremap(pdev->resource[2].start,pdev->resource[2].end - pdev->resource[2].start);
	
	if (gpx2con == NULL) {
		printk("ioremap gpx2con\n");
		ret = -ENOMEM;
		goto ERR1;
	}

	if (gpx1con == NULL) {
		printk("ioremap gpx1con\n");
		ret = -ENOMEM;
		goto ERR2;
	}
	
	if (gpf3con == NULL) {
		printk("ioremap gpf3con\n");
		ret = -ENOMEM;
		goto ERR3;
	}
	gpx2dat = gpx2con + 1;
	gpx1dat = gpx1con + 1;
	gpf3dat = gpf3con + 1;

	
	writel((readl(gpx2con) & ~(0xf << 28)) | (0x1 << 28), gpx2con);
	writel(readl(gpx2dat) | 1 << 7, gpx2dat); //LED_ON
	
	writel((readl(gpx1con) &~ (0xf <<  0))|(0x1 <<  0), gpx1con);
	writel(readl(gpx1dat) | (0x1<<0), gpx1dat);
	
	writel((readl(gpf3con) &~ (0xf << 16))|(0x1 << 16), gpf3con);
	writel(readl(gpf3dat) | (0x1 << 4), gpf3dat);
	
	writel((readl(gpf3con) &~ (0xf << 20))|(0x1 << 20), gpf3con);
	writel(readl(gpf3dat) | (0x1 << 5), gpf3dat);
	
	API_EXIT();
	return 0;
ERR3 :
	iounmap(gpf3con);
ERR2 : 
	iounmap(gpx1con);	
ERR1 : 
	iounmap(gpx2con); 
	return ret;
}

//ioctl
long led_unlocked_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
	API_ENTER();
		switch (cmd) {
			case LED2_ON:
				writel(readl(gpx2dat) | 1 << 7, gpx2dat);
				break;
			case LED2_OFF:
				writel(readl(gpx2dat) & ~(1 << 7), gpx2dat);
				break;
			case LED3_ON:
				//printk("led3 on");
				writel(readl(gpx1dat) | (0x1<<0), gpx1dat);
				break;
			case LED3_OFF:
				//printk("led3 off");
				writel(readl(gpx1dat) & (~(0x1<<0)), gpx1dat);
				break;
			case LED4_ON:
				writel(readl(gpf3dat) | (0x1 << 4), gpf3dat);
				break;
			case LED4_OFF:
				writel(readl(gpf3dat) & ~(0x1 << 4), gpf3dat);
				break;
			case LED5_ON:
				writel(readl(gpf3dat) | (0x1 << 5), gpf3dat);
				break;
			case LED5_OFF:
				writel(readl(gpf3dat) & ~(0x1 << 5), gpf3dat);
				break;
		}
	
	API_EXIT();
	return 0;
}

void led_map_exit(void)
{
	API_ENTER();

	iounmap(gpf3con);
	iounmap(gpx2con);
	iounmap(gpx1con);
	API_EXIT();
}


int led_open (struct inode * inode, struct file * file)
{
	API_ENTER();
	API_EXIT();
	return 0;
}
int led_close (struct inode * inode, struct file * file)
{
	API_ENTER();		
	API_EXIT();
	return 0;
}

static struct file_operations fops = {
	.owner = THIS_MODULE,
	.open  = led_open,	
	.release = led_close,	
	.unlocked_ioctl = led_unlocked_ioctl,
};
int led_init(void)
{
	dev_t devNo = 0;
	int ret = 0;
	int i = 0;
	struct device * pDevice = NULL;
	
	API_ENTER();
	//申请设备号
	devNo = MKDEV(major, minor);
	if (major)
	{
		ret = register_chrdev_region(devNo, DEVICE_COUNT, "led");
	}
	else 
	{
		ret = alloc_chrdev_region(&devNo, minor, DEVICE_COUNT, "led");
	}
	
	if (ret)
	{
		printk("register char devNo error\r\n");
		goto REGISTER_ERR;
	}
	major = MAJOR(devNo);
	minor = MINOR(devNo);
	printk("register success, major = %d, minor = %d\r\n", major, minor);
	//cdev 
	//cdev与操作方式关联 
	cdev_init(&cdev, &fops);
	cdev.owner = THIS_MODULE;
	//cdev与设备号及设备个数关联
	ret = cdev_add(&cdev, devNo, DEVICE_COUNT);
	if (ret)
	{
		printk("cdev add error\r\n");
		goto CDEV_ADD_ERR;
	}
	
	//创建设备类
	pClass = class_create(THIS_MODULE, "ledClass");
	if (IS_ERR(pClass))
	{
		printk("class create error\r\n");
		ret = -EFAULT;
		goto CLASS_CREATE_ERR;
	}
	//创建设备节点
	for (i = 0; i < DEVICE_COUNT; i++)
	{
		pDevice = device_create(pClass, NULL, MKDEV(major, minor + i), NULL, "led%d", i);
		if (IS_ERR(pDevice)) 
		{
			printk("device create error\r\n");
			ret = -EFAULT;
			goto DEVICE_CREATE_ERR;
		}	
	}	
	API_EXIT();
	return 0;
DEVICE_CREATE_ERR:
	class_destroy(pClass);
CLASS_CREATE_ERR:
	cdev_del(&cdev);
CDEV_ADD_ERR:
	unregister_chrdev_region(devNo, DEVICE_COUNT);	
REGISTER_ERR:
	return ret;
}

void led_exit(void)
{
	dev_t devNo;
	int i = 0;
	API_ENTER();
	
	devNo = MKDEV(major, minor);
	//device销毁
	for (i = 0; i < DEVICE_COUNT; i++)
	{
		device_destroy(pClass, MKDEV(major, minor + i));
	}
	//class销毁
	class_destroy(pClass);
	//cdev销毁
	cdev_del(&cdev);
	//释放设备号	
	unregister_chrdev_region(devNo, DEVICE_COUNT);	
	API_EXIT();
}

static int led_probe(struct platform_device *pdev)
{
	int ret = 0;
	API_ENTER();
	//注册设备
	ret = led_init(); //申请设备号、cdev初始化、加载cdev、创建class、创建device节点
	led_map_init(pdev);//ioremap, 初始化led
	API_EXIT();
	return ret;
}

static int led_remove(struct platform_device *pdev)
{
	API_ENTER();
	//注销设备
	led_map_exit();//iounmap
	led_exit();//删除device/class/cdev/devNo
	API_EXIT();
	return 0;
}
static const struct of_device_id led_match_table[] = {
	{ .compatible = "fs4412,leds" },
	{ /* sentinel */ }
};

MODULE_DEVICE_TABLE(of, led_match_table);

//定义一个platform_driver变量
static struct platform_driver led_driver = {
	.probe      = led_probe,
	.remove		= led_remove,
	.driver     = {
		.name   = "led",
		.of_match_table = led_match_table,
	},
};


static int __init led_driver_init(void)
{
	API_ENTER();
	platform_driver_register(&led_driver);
	
	API_EXIT();
	return 0;
}

static void __exit led_driver_exit(void)
{
	API_ENTER();
	platform_driver_unregister(&led_driver);
	
	API_EXIT();
}

module_init(led_driver_init);
module_exit(led_driver_exit);

MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("this is led module for linux 3.5 for FS4412");
MODULE_AUTHOR("Mr.jiang《w_jy2018@163.com》");
MODULE_VERSION("1.0");